package com.netstar.request;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.StatusLine;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.conn.params.ConnManagerParams;
import org.apache.http.conn.params.ConnPerRouteBean;
import org.apache.http.conn.routing.HttpRoute;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;
import org.apache.http.util.EntityUtils;

import com.netstar.util.EvtLog;

public abstract class HttpRequest {

	public static final String SERVER_URL_HTTP = // "http://data.nstar.cc:8080/liuxin2";
	"http://data.nstar.cc:8080/liuxin/";

	public static final String SERVER_URL_HTTPS = "https://data.nstar.cc:8080/liuxin/";

	private static final int CORE_POOL_SIZE = 3;

	private static final int MAX_POOL_SIZE = 4;

	private static final int ALIVE_TIME = 30;

	private static class RequestPoolExecutor extends ThreadPoolExecutor {

		public RequestPoolExecutor() {
			super(CORE_POOL_SIZE, MAX_POOL_SIZE, ALIVE_TIME, TimeUnit.SECONDS,
					new LinkedBlockingQueue<Runnable>(),
					new RejectedExecutionHandler() {

						
						public void rejectedExecution(Runnable r,
								ThreadPoolExecutor executor) {
						}
					});
		}
	}

	protected static ThreadPoolExecutor sExecutor = new RequestPoolExecutor();

	/**
	 * The supported http methods in this app.
	 */
	public static final String GET = "GET", POST = "POST";

	/**
	 * The expected response types.
	 */
	public static final int TYPE_STRING = 1, TYPE_BYTE = 2;

	private static final String TAG = "HttpRequest";

	private boolean mIsReleased = false;

	private long mContentLength = -1;

	private String mContentType;

	private HttpUriRequest mRequest;

	private int mResponseCode = -1;

	private Object mResponse;

	private String mUrl;

	public HttpRequest() {
		final String url = buildURI();
		EvtLog.i(TAG, url);
		mUrl = url;
		try {
			URI uri = new URI(url);
			HttpUriRequest request = null;

			String method = getMethod();
			if (GET.equalsIgnoreCase(method)) {
				request = new HttpGet(uri);
			} else if (POST.equalsIgnoreCase(method)) {
				request = new HttpPost(uri);
			} else {
				throw new IllegalArgumentException("execute(), method "
						+ method + " not supported.");
			}
			mRequest = request;
		} catch (URISyntaxException e) {
		}
	}

	/**
	 * Neither {@link #GET} or {@link #POST}.
	 */
	public abstract String getMethod();

	/**
	 * The path. The SDK will automatically add the base server address prefix
	 * if necessary.
	 */
	public abstract String getPath();

	/**
	 * For HTTP 'GET', this is the store for url parameters, for HTTP
	 * 'POST'(upload file excluded) it will be written to the http request
	 * entity.
	 */
	public abstract HashMap<String, String> getData();

	public HttpUriRequest getRequest() {
		if (mIsReleased) {
			throw new IllegalStateException("connection is already released.");
		}
		return mRequest;
	}

	public String getFinalUrl() {
		if (mIsReleased) {
			throw new IllegalStateException("connection is already released.");
		}
		return mUrl;
	}

	public int getResponseCode() {
		if (mIsReleased) {
			throw new IllegalStateException("connection is already released.");
		}
		return mResponseCode;
	}

	public Object getResponse() {
		if (mIsReleased) {
			throw new IllegalStateException("connection is already released.");
		}
		return mResponse;
	}

	public long getContentLength() {
		if (mIsReleased) {
			throw new IllegalStateException("connection is already released.");
		}
		return mContentLength;
	}

	public String getContentType() {
		if (mIsReleased) {
			throw new IllegalStateException("connection is already released.");
		}
		return mContentType;
	}

	public void releaseResources() {
		if (mIsReleased) {
			return;
		}
		mResponseCode = -1;
		mContentLength = -1;
		mContentType = null;
		mUrl = null;
		mResponse = null;

		mIsReleased = true;
	}

	/**
	 * Neither {@link #TYPE_STRING} or {@link #TYPE_BYTE}.
	 * <p>
	 * Default is {@link #TYPE_STRING}.
	 */
	protected int getExpectedType() {
		return TYPE_STRING;
	}

	protected static ThreadSafeClientConnManager sClientConnManager;

	protected static HttpClient sClient;

	static {
		HttpParams params = new BasicHttpParams();

		ConnPerRouteBean connPerRoute = new ConnPerRouteBean(10);
		/*
		 * Register route for our feed server.
		 */
		connPerRoute.setMaxForRoute(
				new HttpRoute(new HttpHost("data.nstar.cc")), 20);

		ConnManagerParams.setMaxConnectionsPerRoute(params, connPerRoute);
		ConnManagerParams.setMaxTotalConnections(params, 100);
		SchemeRegistry sr = new SchemeRegistry();
		sr.register(new Scheme("http", PlainSocketFactory.getSocketFactory(),
				8080));

		sClientConnManager = new ThreadSafeClientConnManager(params, sr);
		sClient = new DefaultHttpClient(sClientConnManager, params);
	}

	public final void excute() throws ClientProtocolException, IOException,
			URISyntaxException, IllegalStateException {
		if (null == mRequest) {
			throw new URISyntaxException(getFinalUrl(), "");
		}
		HttpClient client = sClient;

		HttpResponse httpResponse = client.execute(mRequest);

		StatusLine sl = httpResponse.getStatusLine();
		if (null != sl) {
			mResponseCode = sl.getStatusCode();
		}

		HttpEntity entity = httpResponse.getEntity();

		if (null != entity) {
			mContentLength = entity.getContentLength();
			Header typeHeader = entity.getContentType();
			if (null != typeHeader) {
				mContentType = typeHeader.getValue();
			}

			final int resType = getExpectedType();
			if (TYPE_STRING == resType) {
				mResponse = EntityUtils.toString(entity, "utf-8");
			} else if (TYPE_BYTE == resType) {
				mResponse = EntityUtils.toByteArray(entity);
			} else {
				throw new IllegalArgumentException(
						"execute(), unknown response type " + resType);
			}
		} else {
			StringBuilder sb = new StringBuilder();
			sb.append("received no response from ");
			sb.append(mUrl);
			throw new IOException(sb.toString());
		}
	}

	/**
	 * Parse a HashMap to List.
	 * 
	 * @return An empty list will returned if params is null or has no key-value
	 *         pair.
	 */
	protected final List<NameValuePair> toList(HashMap<String, String> params) {
		if (null == params) {
			return new ArrayList<NameValuePair>();
		}
		final int size = params.size();
		if (size <= 0) {
			return new ArrayList<NameValuePair>();
		}

		ArrayList<NameValuePair> list = new ArrayList<NameValuePair>(size);
		Set<String> keyset = params.keySet();
		for (String key : keyset) {
			String value = params.get(key);
			list.add(new BasicNameValuePair(key, value));
		}

		return list;
	}

	protected static final int ID_HTTP = 1;
	protected static final int ID_HTTPS = 2;

	protected int getRequetUrlIdentifier() {
		return ID_HTTP;
	}

	private String buildURI() {
		StringBuilder urlBuilder = new StringBuilder();
		final String p = getPath();
		if (!p.startsWith("http://") && !p.startsWith("https://")) {
			int id = getRequetUrlIdentifier();
			if (ID_HTTP == id) {
				urlBuilder.append(SERVER_URL_HTTP);
			} else if (ID_HTTPS == id) {
				urlBuilder.append(SERVER_URL_HTTPS);
			}
			urlBuilder.append(p);
		} else {
			urlBuilder.append(p);
		}
		if (GET.equalsIgnoreCase(getMethod())) {
			List<NameValuePair> params = toList(getData());
			if (params.size() > 0) {
				urlBuilder.append("?");
				urlBuilder.append(URLEncodedUtils.format(params, "utf-8"));
			}
		}
		return urlBuilder.toString();
	}
}
